diff options
Diffstat (limited to 'app/[lng]/evcp/(evcp)/admin/if/items/page.tsx')
| -rw-r--r-- | app/[lng]/evcp/(evcp)/admin/if/items/page.tsx | 371 |
1 files changed, 0 insertions, 371 deletions
diff --git a/app/[lng]/evcp/(evcp)/admin/if/items/page.tsx b/app/[lng]/evcp/(evcp)/admin/if/items/page.tsx deleted file mode 100644 index 5fa788bd..00000000 --- a/app/[lng]/evcp/(evcp)/admin/if/items/page.tsx +++ /dev/null @@ -1,371 +0,0 @@ -import { getOracleConnection } from "@/lib/oracle-db/db"; -import db from "@/db/db"; -import { items } from "@/db/schema/items"; -import { cache } from "react"; -import { Button } from "@/components/ui/button"; -import { - Table, - TableBody, - TableCell, - TableHead, - TableHeader, - TableRow, -} from "@/components/ui/table"; - -// Oracle 메타데이터와 행 타입 정의 -type OracleColumn = { - name: string; -}; - -type OracleRow = (string | number | null)[]; - -// PLM(?)의 Oracle DB 에 직접 연결해 데이터를 가져오는 페이지 -// 모든 데이터를 전부 가져오기는 힘들 수 있으므로 샘플 수준으로 가져오는 것을 우선 node로 처리한다. - -// Oracle에서 데이터를 가져오는 함수를 캐싱 -const fetchOracleData = cache(async (limit = 100, offset = 0) => { - const connection = await getOracleConnection(); - - try { - // 자재마스터 클래스 정보 테이블 조회 (CMCTB_MAT_CLAS) - const result = await connection.execute( - `SELECT - CLAS_CD, - CLAS_NM, - CLAS_DTL, - PRNT_CLAS_CD, - CLAS_LVL, - DEL_ORDR, - UOM, - STYPE, - GRD_MATL, - CHG_DT, - BSE_UOM - FROM SHI1.CMCTB_MAT_CLAS - WHERE ROWNUM <= :limit + :offset - OFFSET :offset ROWS`, - { limit, offset } - ); - - // 총 레코드 수 조회 - const countResult = await connection.execute( - `SELECT COUNT(*) AS TOTAL FROM SHI1.CMCTB_MAT_CLAS` - ); - - const totalCount = countResult.rows?.[0]?.[0] || 0; - - return { - rows: result.rows as OracleRow[] || [], - metadata: result.metaData as OracleColumn[] || [], - totalCount - }; - } catch (error) { - console.error("Oracle 데이터 조회 오류:", error); - return { rows: [], metadata: [], totalCount: 0 }; - } finally { - // 연결 종료 - if (connection) { - try { - await connection.close(); - } catch (err) { - console.error("Oracle 연결 종료 오류:", err); - } - } - } -}); - -// 전체 데이터를 가져와 Postgres에 삽입하는 함수 -const syncAllDataToPostgres = cache(async () => { - const BATCH_SIZE = 1000; // 한 번에 처리할 레코드 수 - const MAX_RECORDS = 50000; // 최대 처리할 레코드 수 - - try { - // 총 레코드 수 확인 - const { totalCount } = await fetchOracleData(1, 0); - const recordsToProcess = Math.min(totalCount, MAX_RECORDS); - - let processedCount = 0; - let currentOffset = 0; - - // 배치 단위로 처리 - while (processedCount < recordsToProcess) { - // 오라클에서 데이터 가져오기 - const { rows, metadata } = await fetchOracleData(BATCH_SIZE, currentOffset); - - if (!rows.length) break; - - // Postgres DB 트랜잭션 시작 - await db.transaction(async (tx) => { - // Oracle 데이터를 Postgres 스키마에 맞게 변환하여 삽입 (UPSERT) - for (const row of rows) { - // 배열 형태의 데이터를 객체로 변환 - const rowObj: Record<string, string | number | null> = {}; - metadata.forEach((col: OracleColumn, index: number) => { - rowObj[col.name] = row[index]; - }); - - await tx - .insert(items) - .values({ - itemCode: String(rowObj.CLAS_CD || ''), - itemName: String(rowObj.CLAS_NM || ''), - description: rowObj.CLAS_DTL ? String(rowObj.CLAS_DTL) : null, - parentItemCode: rowObj.PRNT_CLAS_CD ? String(rowObj.PRNT_CLAS_CD) : null, - itemLevel: typeof rowObj.CLAS_LVL === 'number' ? rowObj.CLAS_LVL : null, - deleteFlag: rowObj.DEL_ORDR ? String(rowObj.DEL_ORDR) : null, - unitOfMeasure: rowObj.UOM ? String(rowObj.UOM) : null, - steelType: rowObj.STYPE ? String(rowObj.STYPE) : null, - gradeMaterial: rowObj.GRD_MATL ? String(rowObj.GRD_MATL) : null, - changeDate: rowObj.CHG_DT ? String(rowObj.CHG_DT) : null, - baseUnitOfMeasure: rowObj.BSE_UOM ? String(rowObj.BSE_UOM) : null - }) - .onConflictDoUpdate({ - target: items.itemCode, - set: { - itemName: String(rowObj.CLAS_NM || ''), - description: rowObj.CLAS_DTL ? String(rowObj.CLAS_DTL) : null, - parentItemCode: rowObj.PRNT_CLAS_CD ? String(rowObj.PRNT_CLAS_CD) : null, - itemLevel: typeof rowObj.CLAS_LVL === 'number' ? rowObj.CLAS_LVL : null, - deleteFlag: rowObj.DEL_ORDR ? String(rowObj.DEL_ORDR) : null, - unitOfMeasure: rowObj.UOM ? String(rowObj.UOM) : null, - steelType: rowObj.STYPE ? String(rowObj.STYPE) : null, - gradeMaterial: rowObj.GRD_MATL ? String(rowObj.GRD_MATL) : null, - changeDate: rowObj.CHG_DT ? String(rowObj.CHG_DT) : null, - baseUnitOfMeasure: rowObj.BSE_UOM ? String(rowObj.BSE_UOM) : null, - updatedAt: new Date() - } - }); - } - }); - - processedCount += rows.length; - currentOffset += BATCH_SIZE; - - // 진행 상황 업데이트 - const progress = Math.min(100, Math.round((processedCount / recordsToProcess) * 100)); - - // 임시 상태 저장 (실제 구현에서는 저장소나 상태 관리 도구를 사용) - console.log(`진행 상황: ${progress}% (${processedCount}/${recordsToProcess})`); - } - - return { - success: true, - message: `${processedCount}개의 자재마스터 클래스 정보가 items 테이블로 성공적으로 이관되었습니다.`, - count: processedCount - }; - } catch (error) { - console.error("Postgres 데이터 이관 오류:", error); - return { - success: false, - message: `데이터 이관 중 오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`, - count: 0 - }; - } -}); - -// 샘플 데이터만 Postgres에 삽입하는 함수 -const syncSampleDataToPostgres = cache(async () => { - const { rows, metadata } = await fetchOracleData(100, 0); - - if (!rows.length) { - return { - success: false, - message: "Oracle에서 가져올 데이터가 없습니다.", - count: 0 - }; - } - - try { - // Postgres DB 트랜잭션 시작 - await db.transaction(async (tx) => { - // Oracle 데이터를 Postgres 스키마에 맞게 변환하여 삽입 (UPSERT) - for (const row of rows) { - // 배열 형태의 데이터를 객체로 변환 - const rowObj: Record<string, string | number | null> = {}; - metadata.forEach((col: OracleColumn, index: number) => { - rowObj[col.name] = row[index]; - }); - - await tx - .insert(items) - .values({ - itemCode: String(rowObj.CLAS_CD || ''), - itemName: String(rowObj.CLAS_NM || ''), - description: rowObj.CLAS_DTL ? String(rowObj.CLAS_DTL) : null, - parentItemCode: rowObj.PRNT_CLAS_CD ? String(rowObj.PRNT_CLAS_CD) : null, - itemLevel: typeof rowObj.CLAS_LVL === 'number' ? rowObj.CLAS_LVL : null, - deleteFlag: rowObj.DEL_ORDR ? String(rowObj.DEL_ORDR) : null, - unitOfMeasure: rowObj.UOM ? String(rowObj.UOM) : null, - steelType: rowObj.STYPE ? String(rowObj.STYPE) : null, - gradeMaterial: rowObj.GRD_MATL ? String(rowObj.GRD_MATL) : null, - changeDate: rowObj.CHG_DT ? String(rowObj.CHG_DT) : null, - baseUnitOfMeasure: rowObj.BSE_UOM ? String(rowObj.BSE_UOM) : null - }) - .onConflictDoUpdate({ - target: items.itemCode, - set: { - itemName: String(rowObj.CLAS_NM || ''), - description: rowObj.CLAS_DTL ? String(rowObj.CLAS_DTL) : null, - parentItemCode: rowObj.PRNT_CLAS_CD ? String(rowObj.PRNT_CLAS_CD) : null, - itemLevel: typeof rowObj.CLAS_LVL === 'number' ? rowObj.CLAS_LVL : null, - deleteFlag: rowObj.DEL_ORDR ? String(rowObj.DEL_ORDR) : null, - unitOfMeasure: rowObj.UOM ? String(rowObj.UOM) : null, - steelType: rowObj.STYPE ? String(rowObj.STYPE) : null, - gradeMaterial: rowObj.GRD_MATL ? String(rowObj.GRD_MATL) : null, - changeDate: rowObj.CHG_DT ? String(rowObj.CHG_DT) : null, - baseUnitOfMeasure: rowObj.BSE_UOM ? String(rowObj.BSE_UOM) : null, - updatedAt: new Date() - } - }); - } - }); - - return { - success: true, - message: `${rows.length}개의 자재마스터 클래스 정보가 items 테이블로 성공적으로 이관되었습니다.`, - count: rows.length - }; - } catch (error) { - console.error("Postgres 데이터 삽입 오류:", error); - return { - success: false, - message: `데이터 이관 중 오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`, - count: 0 - }; - } -}); - -// 현재 PostgreSQL DB에 저장된 데이터를 조회하는 함수 -const fetchCurrentPgData = cache(async () => { - try { - return await db.select().from(items).limit(100); - } catch (error) { - console.error("Postgres 데이터 조회 오류:", error); - return []; - } -}); - -export default async function ItemsAdminPage() { - // 데이터 초기 로드 - const { rows: oracleData, metadata, totalCount } = await fetchOracleData(100, 0); - const pgData = await fetchCurrentPgData(); - - // 서버 액션으로 샘플 데이터 동기화 수행 - async function handleSyncSample() { - "use server"; - await syncSampleDataToPostgres(); - // 반환 없이 void로 처리 - } - - // 서버 액션으로 전체 데이터 동기화 수행 - async function handleSyncAll() { - "use server"; - await syncAllDataToPostgres(); - // 반환 없이 void로 처리 - } - - return ( - <div className="p-8 space-y-6"> - <h1 className="text-2xl font-bold">Items 테이블 데이터 관리</h1> - <p className="text-muted-foreground">PLM의 Oracle DB에서 자재마스터 클래스 정보를 Items 테이블로 이관하는 페이지입니다.</p> - - <div className="grid grid-cols-1 md:grid-cols-2 gap-8"> - <div className="border rounded-lg"> - <div className="p-4 border-b bg-muted/50"> - <h2 className="text-xl font-semibold">Oracle DB 데이터</h2> - <p className="text-sm text-muted-foreground">총 {totalCount}개 중 {oracleData.length}개의 레코드</p> - </div> - - <div className="overflow-auto max-h-80 p-4"> - <Table> - <TableHeader> - <TableRow> - <TableHead>클래스코드</TableHead> - <TableHead>클래스명</TableHead> - <TableHead>클래스내역</TableHead> - <TableHead>레벨</TableHead> - </TableRow> - </TableHeader> - <TableBody> - {oracleData.map((row: OracleRow, idx: number) => { - // 컬럼 인덱스 찾기 - const getColIndex = (name: string) => metadata.findIndex((col: OracleColumn) => col.name === name); - const classCdIdx = getColIndex('CLAS_CD'); - const clasNmIdx = getColIndex('CLAS_NM'); - const clasDtlIdx = getColIndex('CLAS_DTL'); - const clasLvlIdx = getColIndex('CLAS_LVL'); - - return ( - <TableRow key={idx}> - <TableCell>{row[classCdIdx]}</TableCell> - <TableCell>{row[clasNmIdx]}</TableCell> - <TableCell>{row[clasDtlIdx]}</TableCell> - <TableCell>{row[clasLvlIdx]}</TableCell> - </TableRow> - ); - })} - </TableBody> - </Table> - </div> - </div> - - <div className="border rounded-lg"> - <div className="p-4 border-b bg-muted/50"> - <h2 className="text-xl font-semibold">Items 테이블 데이터</h2> - <p className="text-sm text-muted-foreground">총 {pgData.length}개의 레코드</p> - </div> - - <div className="overflow-auto max-h-80 p-4"> - <Table> - <TableHeader> - <TableRow> - <TableHead>ID</TableHead> - <TableHead>아이템코드</TableHead> - <TableHead>아이템명</TableHead> - <TableHead>레벨</TableHead> - </TableRow> - </TableHeader> - <TableBody> - {pgData.map((row) => ( - <TableRow key={row.id}> - <TableCell>{row.id}</TableCell> - <TableCell>{row.itemCode}</TableCell> - <TableCell>{row.itemName}</TableCell> - <TableCell>{row.itemLevel}</TableCell> - </TableRow> - ))} - </TableBody> - </Table> - </div> - </div> - </div> - - <div className="flex flex-col gap-6 mt-8"> - <div className="flex flex-wrap gap-4"> - <form action={handleSyncSample}> - <Button type="submit" variant="default"> - 샘플 데이터 이관 (100건) - </Button> - </form> - - <form action={handleSyncAll}> - <Button type="submit" variant="secondary"> - 전체 데이터 이관 (최대 50,000건) - </Button> - </form> - </div> - - <div className="border rounded-lg p-4"> - <h3 className="text-lg font-semibold mb-2">데이터 이관 시 참고사항</h3> - <ul className="list-disc list-inside space-y-1 text-sm text-muted-foreground"> - <li>샘플 데이터 이관은 100건의 데이터만 Items 테이블에 저장합니다.</li> - <li>전체 데이터 이관은 1,000건씩 나누어 최대 50,000건까지 처리합니다.</li> - <li>데이터 양이 많을 경우 이관 작업에 시간이 소요될 수 있습니다.</li> - <li>기존 데이터는 유지하고 아이템코드가 같은 경우 업데이트합니다 (UPSERT).</li> - <li>Oracle의 CMCTB_MAT_CLAS 테이블 데이터를 Items 테이블로 매핑합니다.</li> - </ul> - </div> - </div> - </div> - ); -}
\ No newline at end of file |
